/*
 *  shnode.cpp
 *  shaderz_v3
 *
 *  Created by Anjali on 3/23/11.
 *  Copyright 2011 __MyCompanyName__. All rights reserved.
 *
 */
#include <math.h>

#include "shnode.h"

using namespace shaderz;

std::list<shNode*>	shNode::mAllNodes;

shNode::shNode()
	: mNodeName()
{
	// add this node to the all node list
	mAllNodes.push_back(this);
}

shNode::shNode( const std::string& InNodeName )
	: mNodeName(InNodeName)
{
	// add this node to the all node list
	mAllNodes.push_back(this);	
}

shNode::~shNode()
{
#warning Fixme: remove this node's entry from the allNode list
	// mAllNodes.erase(mAllNodes.find(this));
	
	// todo: disconnect all nodes connected to this one before destruction
	int stubIndex, linkIndex;
	
	// disconnect all input connections
	for( stubIndex=0; stubIndex < mInputStubs.size(); ++stubIndex )
	{
		linkStub* currentStub = mInputStubs[stubIndex];
		for( linkIndex=0; linkIndex < currentStub->allLinks.size(); ++linkIndex )
		{
			shNode* pOtherNode = currentStub->allLinks[linkIndex].otherNode;
			pOtherNode->disconnectFrom(this);
		}
		
		// finally release memeory
		delete currentStub;
	}
	
	// disconnect all output connections
	for( stubIndex=0; stubIndex < mOutputStubs.size(); ++stubIndex )
	{
		linkStub* currentStub = mOutputStubs[stubIndex];
		for( linkIndex=0; linkIndex < currentStub->allLinks.size(); ++linkIndex )
		{
			shNode* pOtherNode = currentStub->allLinks[linkIndex].otherNode;
			pOtherNode->disconnectFrom(this);
		}
		
		// finally release memory
		delete currentStub;
	}
}

shNode::linkStub* shNode::constructLinkStub( const std::string& InLinkName, linkIOType InIOType, linkStubType InType )
{
	linkStub* pNewStub = new shNode::linkStub;
	pNewStub->name = InLinkName;
	pNewStub->ioType = InIOType;
	pNewStub->type = InType;
	
	return pNewStub;
}

/**
 *	Iterated through the input and output stubs of this node to locate the desired stub
 *
 *	@param	InStubName		The name of the stub we are interested in
 *
 *	@returns	The stub pointer of the stub found. NULL on failure.
 */
shNode::linkStub* shNode::findStub( std::string InStubName )
{
	linkStub* locatedStub = 0;
	int index;
	
	// search in input stubs
	for( index=0; index < mInputStubs.size(); ++index )
	{
		if( mInputStubs[index]->name == InStubName )
		{
			locatedStub = mInputStubs[index];
			break;
		}
	}
	
	// serach in output stubs
	for( index=0; !locatedStub && index < mOutputStubs.size(); ++index )
	{
		if( mOutputStubs[index]->name == InStubName )
		{
			locatedStub = mOutputStubs[index];
			break;
		}
	}

	return locatedStub;
}

// const version of the findStub function
const shNode::linkStub* shNode::findStub( std::string InStubName ) const
{
	const linkStub* locatedStub = 0;
	int index;
	
	// search in input stubs
	for( index=0; index < mInputStubs.size(); ++index )
	{
		if( mInputStubs[index]->name == InStubName )
		{
			locatedStub = mInputStubs[index];
			break;
		}
	}
	
	// serach in output stubs
	for( index=0; !locatedStub && index < mOutputStubs.size(); ++index )
	{
		if( mOutputStubs[index]->name == InStubName )
		{
			locatedStub = mOutputStubs[index];
			break;
		}
	}

	return locatedStub;
}

// checks for stub compatibility using 
// a) Equality checks 
// b) upcasting possiblity
bool shNode::areStubsCompatible( linkStubType InType1, linkStubType InType2 )
{
	return (InType1 == InType2) || isUpcastingPossible(InType1, InType2);
}

// can we upcast from type1 to type2?
bool shNode::isUpcastingPossible( linkStubType InType1, linkStubType InType2 )
{
	bool bCanUpcast = false;
	switch(InType1)
	{
	case SHST_CONST1VEC:
		bCanUpcast = (InType2 == SHST_CONST2VEC || InType2 == SHST_CONST3VEC || InType2 == SHST_CONST4VEC);
		break;
		
	case SHST_CONST2VEC:
		bCanUpcast = (InType2 == SHST_CONST3VEC || InType2 == SHST_CONST4VEC);
		break;
	
	case SHST_CONST3VEC:
		bCanUpcast = (InType2 == SHST_CONST4VEC);
		break;
		
	case SHST_TEXTURE:
		// no upcasting possible
		break;
	}
	
	return bCanUpcast;
}

void shNode::addInputStub( shNode::linkStub* InNewStub )
{	
	mInputStubs.push_back(InNewStub);
}
		
void shNode::addOutputStub( shNode::linkStub* InNewStub )
{
	mOutputStubs.push_back(InNewStub);
}

#warning todo: Fix connect() to make sure that no loops are introduced
// connects a stub on this node to a stub on other node
bool shNode::connect( std::string InStubNameToConnect, shNode* InOtherNode, std::string InOtherStubName )
{
	bool bConnectionSucceeded = false;
	linkStub* thisStub = findStub(InStubNameToConnect);
	linkStub* otherStub = (InOtherNode) ? InOtherNode->findStub(InOtherStubName) : 0;
	
	// for the input stub, make sure that all other connections are broken before establishing this one
	linkStub* inputStub = thisStub->ioType == SHIO_INPUT ? thisStub : otherStub;
	linkStub* outputStub = thisStub->ioType == SHIO_OUTPUT ? thisStub : otherStub;
	shNode* inputNode = thisStub->ioType == SHIO_INPUT ? this : InOtherNode;
	
	if( thisStub 
		&& otherStub
		&& thisStub->ioType != otherStub->ioType /*input of one can connect to output of the other*/
		&& areStubsCompatible(outputStub->type, inputStub->type) )
	{
		if( inputStub->allLinks.size() > 0 )
		{
			// should have only one link if any
			inputStub->allLinks[0].otherNode->disconnectFrom( inputNode, inputStub->name );
			inputStub->allLinks.clear();
		}
		
		// package the link information and add it to both source and receiver
		link thisToOtherLinkInfo = { InOtherNode, InOtherStubName };
		thisStub->allLinks.push_back(thisToOtherLinkInfo);
		
		link otherToThisLinkInfo = { this, InStubNameToConnect };
		otherStub->allLinks.push_back(otherToThisLinkInfo);

		bConnectionSucceeded = true;
	}
	
	return bConnectionSucceeded;
}

// breaks all links to the given node
void shNode::disconnectFrom( shNode* InOtherNode, std::string InStubName )
{
	int index, linkIndex;
	
	// parse the input stubs
	for( index=0; index < mInputStubs.size(); ++index )
	{
		linkStub* currentStub = mInputStubs[index];
		
		// each input stub should be connected only to one node
		if( currentStub->allLinks.size() > 1 )
		{
			// flag an error
		}
		
		// if this input stub is connected to the given node, clear this entry effectively breaking the connection
		if( currentStub->allLinks.size() > 0 
			&& currentStub->allLinks[0].otherNode == InOtherNode
			&& (InStubName == "" || InStubName == currentStub->allLinks[0].otherStubName) )
		{
			currentStub->allLinks.clear();
		} 
	}
		
	// parse the output stubs
	for( index=0; index < mOutputStubs.size(); ++index )
	{
		linkStub* currentStub = mOutputStubs[index];
		std::vector<std::vector<link>::iterator> itemsToDelete;
		
		// pass1 - collect the items to be deleted
		for( std::vector<link>::iterator itr = currentStub->allLinks.begin(); itr != currentStub->allLinks.end(); ++itr )
		{
			if( itr->otherNode == InOtherNode && (InStubName == "" || InStubName == itr->otherStubName))
			{
				itemsToDelete.push_back(itr);
			}
		}
		
		// delete the collected item list
		for( int deleteIndex=0; deleteIndex < itemsToDelete.size(); ++deleteIndex )
		{
			currentStub->allLinks.erase( itemsToDelete[deleteIndex] );
		}
	}
}

void shNode::evaluate()
{
	// iterate over the inputs to make sure that they are evaluated
	int inputIndex;
	for( inputIndex=0; inputIndex < mInputStubs.size(); ++inputIndex )
	{
		// input links have only one input
		if( mInputStubs[inputIndex]->allLinks.size() > 0 )
		{
			shNode* otherNode = mInputStubs[inputIndex]->allLinks[0].otherNode;
			linkStub* otherLinkStub = otherNode ? otherNode->findStub( mInputStubs[inputIndex]->allLinks[0].otherStubName ) : 0;
			if( otherNode && otherLinkStub )
			{
				// evaluate the node
				otherNode->evaluate();
				
				// transfer the evaluated value to the current input stub
				*mInputStubs[inputIndex] = *otherLinkStub;
			}
		}
	}
}

// renames this node
void shNode::rename( std::string InNewNodeName )
{
	mNodeName = InNewNodeName;
}

// renames a stub in this node from oldName to newName
void shNode::renameStub( std::string InOldStubName, std::string InNewStubName )
{
	// rename the required stub
	linkStub* pStub = findStub(InOldStubName);
	if( pStub )
	{
		pStub->name = InNewStubName;
		
		// now rename all stubs linked to this stub
		int linkIndex;
		for( linkIndex=0; linkIndex < pStub->allLinks.size(); ++linkIndex )
		{
			link& linkInfo = pStub->allLinks[linkIndex];
			linkStub* pOtherStub = linkInfo.otherNode->findStub( linkInfo.otherStubName );
			if( pOtherStub )
			{
				// iterate through the links to find a link to this node.
				for( int otherLinkIndex=0; otherLinkIndex < pOtherStub->allLinks.size(); ++otherLinkIndex )
				{
					link& otherLinkInfo = pOtherStub->allLinks[otherLinkIndex];
					
					// rename the stub associated with this node
					if( otherLinkInfo.otherNode == this && otherLinkInfo.otherStubName == InOldStubName )
					{
						otherLinkInfo.otherStubName = InNewStubName;
					}
				}
			}
		}
	}
}

shNode::linkStub& shNode::linkStub::operator +( const shNode::linkStub& InRhs )
{
	switch(type)
	{
	case SHST_CONST1VEC:
		value.constant1Vec += InRhs.value.constant1Vec;
		break;
		
	case SHST_CONST2VEC:
		value.constant2Vec[0] += InRhs.value.constant2Vec[0];
		value.constant2Vec[1] += InRhs.value.constant2Vec[1];
		break;

	case SHST_CONST3VEC:
		value.constant3Vec[0] += InRhs.value.constant3Vec[0];
		value.constant3Vec[1] += InRhs.value.constant3Vec[1];
		value.constant3Vec[2] += InRhs.value.constant3Vec[2];
		break;

	case SHST_CONST4VEC:
		value.constant4Vec[0] += InRhs.value.constant4Vec[0];
		value.constant4Vec[1] += InRhs.value.constant4Vec[1];
		value.constant4Vec[2] += InRhs.value.constant4Vec[2];
		value.constant4Vec[3] += InRhs.value.constant4Vec[3];
		break;
	}
	
	return *this;
}

shNode::linkStub& shNode::linkStub::operator -( const shNode::linkStub& InRhs )
{
	switch(type)
	{
	case SHST_CONST1VEC:
		value.constant1Vec -= InRhs.value.constant1Vec;
		break;
		
	case SHST_CONST2VEC:
		value.constant2Vec[0] -= InRhs.value.constant2Vec[0];
		value.constant2Vec[1] -= InRhs.value.constant2Vec[1];
		break;

	case SHST_CONST3VEC:
		value.constant3Vec[0] -= InRhs.value.constant3Vec[0];
		value.constant3Vec[1] -= InRhs.value.constant3Vec[1];
		value.constant3Vec[2] -= InRhs.value.constant3Vec[2];
		break;

	case SHST_CONST4VEC:
		value.constant4Vec[0] -= InRhs.value.constant4Vec[0];
		value.constant4Vec[1] -= InRhs.value.constant4Vec[1];
		value.constant4Vec[2] -= InRhs.value.constant4Vec[2];
		value.constant4Vec[3] -= InRhs.value.constant4Vec[3];
		break;
	}
	
	return *this;
}

shNode::linkStub& shNode::linkStub::operator *( const shNode::linkStub& InRhs )
{
	switch(type)
	{
	case SHST_CONST1VEC:
		value.constant1Vec *= InRhs.value.constant1Vec;
		break;
		
	case SHST_CONST2VEC:
		value.constant2Vec[0] *= InRhs.value.constant2Vec[0];
		value.constant2Vec[1] *= InRhs.value.constant2Vec[1];
		break;

	case SHST_CONST3VEC:
		value.constant3Vec[0] *= InRhs.value.constant3Vec[0];
		value.constant3Vec[1] *= InRhs.value.constant3Vec[1];
		value.constant3Vec[2] *= InRhs.value.constant3Vec[2];
		break;

	case SHST_CONST4VEC:
		value.constant4Vec[0] *= InRhs.value.constant4Vec[0];
		value.constant4Vec[1] *= InRhs.value.constant4Vec[1];
		value.constant4Vec[2] *= InRhs.value.constant4Vec[2];
		value.constant4Vec[3] *= InRhs.value.constant4Vec[3];
		break;
	}
	
	return *this;
}

shNode::linkStub& shNode::linkStub::operator /( const shNode::linkStub& InRhs )
{
#define SAFE_DIVIDE(a,b)	if( fabs(b) > KINDA_SMALL_NUMBER ) \
							{ \
								(a) /= (b); \
							}
	switch(type)
	{
	case SHST_CONST1VEC:
		SAFE_DIVIDE( value.constant1Vec, InRhs.value.constant1Vec );
		break;
		
	case SHST_CONST2VEC:
		SAFE_DIVIDE( value.constant2Vec[0], InRhs.value.constant2Vec[0]);
		SAFE_DIVIDE( value.constant2Vec[1], InRhs.value.constant2Vec[1]);
		break;

	case SHST_CONST3VEC:
		SAFE_DIVIDE( value.constant3Vec[0], InRhs.value.constant3Vec[0]);
		SAFE_DIVIDE( value.constant3Vec[1], InRhs.value.constant3Vec[1]);
		SAFE_DIVIDE( value.constant3Vec[2], InRhs.value.constant3Vec[2]);
		break;

	case SHST_CONST4VEC:
		SAFE_DIVIDE( value.constant4Vec[0], InRhs.value.constant4Vec[0]);
		SAFE_DIVIDE( value.constant4Vec[1], InRhs.value.constant4Vec[1]);
		SAFE_DIVIDE( value.constant4Vec[2], InRhs.value.constant4Vec[2]);
		SAFE_DIVIDE( value.constant4Vec[3], InRhs.value.constant4Vec[3]);
		break;
	}
#undef SAFE_DIVIDE
	
	return *this;
}

shNode::linkStub& shNode::linkStub::operator =( const shNode::linkStub& InRhs )
{				
	value = InRhs.value;
	ioType = InRhs.ioType;
	name = InRhs.name;
	type = InRhs.type;
	allLinks = InRhs.allLinks;
	
	return *this;
}

// clears the value part of the link Stub
void shNode::linkStub::clear()
{
	memset( &value, 0, sizeof(value) );
}

// converts this node to a string
std::string shNode::linkStub::toString() const
{
	char buff[100];
	std::string nodeString = name + ": ";
	switch( type )
	{
	case SHST_CONST1VEC:
		snprintf( buff, sizeof(buff), "%0.4f", value.constant1Vec );
		nodeString += buff;
		break;
		
	case SHST_CONST2VEC:
		snprintf( buff, sizeof(buff), "%0.4f, %0.4f", value.constant2Vec[0], value.constant2Vec[1] );
		nodeString += buff;
		break;

	case SHST_CONST3VEC:
		snprintf( buff, sizeof(buff), "%0.4f, %0.4f, %0.4f", value.constant3Vec[0], value.constant3Vec[1], value.constant3Vec[2] );
		nodeString += buff;
		break;

	case SHST_CONST4VEC:
		snprintf( buff, sizeof(buff), "%0.4f, %0.4f, %0.4f, %0.4f", value.constant4Vec[0], value.constant4Vec[1], value.constant4Vec[2], value.constant4Vec[3] );
		nodeString += buff;
		break;
	}

	return nodeString;
}

#ifdef	SH_DEBUG
#include <iostream>
void shNode::dumpNodeStats() const
{
	std::cout<<"Node name: "<<name()<<std::endl;
	std::cout<<"\tInput stubs - "<<mInputStubs.size()<<std::endl;
	for( int index=0; index < mInputStubs.size(); ++index )
	{
		std::cout	<<"\t\t"
					<< mInputStubs[index]->name
					<<" type: "<<mInputStubs[index]->type 
					<<" ## "
					<<mInputStubs[index]->toString() 
					<<std::endl;
		for( int connectionIndex=0; connectionIndex < mInputStubs[index]->allLinks.size(); ++connectionIndex )
		{
			std::cout<<"\t\t\tIncommingLink ["	<<mInputStubs[index]->allLinks[connectionIndex].otherNode->name()
									<<" # "<<mInputStubs[index]->allLinks[connectionIndex].otherStubName
									<<"]"
									<<std::endl;
		}
	}
	std::cout<<"\tOutput stubs - "<< mOutputStubs.size()<< std::endl;
	for( int index=0; index < mOutputStubs.size(); ++index )
	{
		std::cout	<<"\t\t"
					<< mOutputStubs[index]->name
					<<" type: "<< mOutputStubs[index]->type
					<<" ## "
					<< mOutputStubs[index]->toString()
					<< std::endl;
		for( int connectionIndex=0; connectionIndex < mOutputStubs[index]->allLinks.size(); ++connectionIndex )
		{
			std::cout<<"\t\t\tOutgoingLink ["<<mOutputStubs[index]->allLinks[connectionIndex].otherNode->name()
									<<" # "<<mOutputStubs[index]->allLinks[connectionIndex].otherStubName
									<<"]"
									<<std::endl;
		}
	}
}
#endif